1. Field of the Invention
This invention relates generally to the Java™ platform, and more particularly to methods and systems for minimizing main memory consumption associated with defining class loading policies in Java.
2. Description of the Related Art
Today, several high-level programming languages are being offered to the computer programmers and software developers, one of which is Java™. Pervasive use of Java in a relatively short period of time can at least in part, be credited to Java platform independence, object orientation, and dynamic nature. Java also eliminates many of the monotonous and error-prone tasks performed by an application programmer or developer, including memory management and cross-platform porting. In this manner, Java has liberated programmers to focus on the design and functionalities of the applications.
One of many distinctive features of Java programming language, and one that is extensively exploited by application programmers, is the ability to program class loaders and defining class loading policies. Currently, a wide range of applications exploit class loaders including, scripting environment with runtime introspection capabilities, Integrated Development Environments (IDEs), bytecode transformation tools, aspect-oriented programming environment, web browsers, servlet engines, and application servers.
Class loaders (herein also referred to as “loaders”) are mechanisms for dynamically loading software components in a running program. Loaders allow a program to define a class in separate namespaces and to control the location from where the architecturally-neutral definition of the class can be obtained. Having the ability to create separate namespaces for a class allows a program to load the same or different definitions of a class multiple times using the same name. Although a class loaded by different loaders has the same name and may have the same definition, the loaded classes, are treated as distinct types. In this manner, isolation is provided to some extent such that different software components can be loaded by a software program without any interference, even though software components may contain classes having the same name. For instance, an applet container for a web-browser can load multiple applets using different loaders. Although the classes loaded for each applet may have the same name, the Java virtual machine (JVM) can treat the loaded classes as if the classes have different types.
Loaders can also provide the opportunity to transparently enhance code generated at runtime by a third-party. For instance, loaders allow interception of the code and modification of the code (via bytecode transformation) before the code is linked with the rest of the program that is running. Loaders also allow software programs to generate and load a class file at runtime.
Using class loaders, however, does not come without a cost. For instance, current implementations of the JVM typically create a runtime representation of a class in memory for each class loader that defines the class. Creating the runtime representation is performed even though another class having the same name and architecturally-neutral representation has already been created by a different loader. As a consequence, the effort in creating an optimized runtime representation is needlessly repeated and the runtime representation of classes are unnecessarily replicated in memory. Examples of such replicated efforts include, repeating the same parsing of the same class file, construction of a main-memory runtime representation, bytecode verification, quickening of bytecodes, resolution of constants and symbols, and identification and dynamic compilation of frequently used methods.
One way application programmers can circumvent the cost associated with replicating the entire runtime representation of a class is using delegation relationships between class loaders. Namely, when a class loader has been requested to load a class, the class loader may delegate the definition of the class to a different class loader that has already defined the class. Unfortunately, as the complexity degree of delegation relationships increases so can the extent and possibility of generating errors. Additionally, delegating the definition of a class to another class loader may not always be possible per-design. For instance, delegation relationships cannot be used in a situation where software components are to be isolated, as required by an application (e.g., loading multiple applets in an applet container).
There have been certain attempts to share the main-memory runtime representation of classes between multiple software programs being executed. One approach has been to launch a separate operating system (OS) process to execute separate instances of the JVM for every Java program, and to store the sharable part of the runtime representation of classes in a memory area shared among the operating system processes.
Another solution is to encode the entire runtime representation of classes in a binary format that is natively supported by the host OS shared libraries mechanism. For instance, the main-memory representation of classes can be encoded in the Extremely Low Frequency (ELF) format, thus generating binary codes that can be relocated. In such a scenario, loading and relocation of the binary codes are performed by a linker of the OS.
Yet another solution is to share the runtime representation of classes serially. In the latter approach, a JVM that has been launched once can be re-used by an arbitrary number of software programs as long as the software programs adhere to a set of rules analogous to the rules required in a middleware environment (e.g., Java 2 Platform, Enterprise Edition (J2EE)). However, in such an approach, software programs cannot use Abstract Windowing Toolkit (AWT), set global static variables of classes shared serially, start arbitrary threads, use native methods, etc.). Failure to comply with the set of rules prompts the creation of a new JVM instance, and thus, the associated costs.
Still another solution is to collocate all Java programs in the same OS process, and to execute the Java programs with a single JVM capable of multi-tasking.
Nevertheless, all of the above-mentioned approaches, share the same limitations. Specifically, none of the approaches is capable of sharing, to any degree, the runtime representation of classes defined by arbitrary user-defined class loaders. Rather, mostly classes defined by the boot class loader (i.e., the loader used at JVM startup to load the so-called “core” classes that are associated with the JVM implementation (e.g., java.lang.Object, java.lang.Class, etc.)), or the system class loader (i.e., class loader used to load the class defining the entry point to a software program (i.e., the static main (String [ ]) method of the class, etc.), can have respective runtime representations shared by multiple software programs. Limiting sharing of runtime representation of classes to the loaders being fully controlled by the JVM (e.g., the boot and system loaders) can simplify re-entrance by preventing the symbolic links to resolve into potentially different definitions of the same class.
In contrast to the classes defined by the boot class loader or the system class loader, user-defined class loaders can implement any number of arbitrary policies to obtain the definition of a class (including, but not limited to, modifying a class file fetched from a known location on the fly, or generating a class file from scratch on the fly, etc.). As a consequence, two loaders that define the same class might resolve symbolic links being used by the class differently. None of the above-mentioned approaches, however, allow sharing of the runtime representation of classes under such condition. Hence, none of the above-mentioned approaches can address the problem of reducing the footprint of the software programs exploiting user-defined class loaders.
In view of the foregoing, a need therefore exists in the art for systems and methods capable of increasing main memory conservation by allowing sharing of runtime representation of software components by a plurality of component loaders.